<?php
/*
* Tnotifier - Twitter notifier for Ubuntu
*
* Copyright (C) 2010 Burak Yueksel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* @author Burak Yueksel <burak.yueksel@gmail.com>
* @version 1.0.0
* @version $Id: tnotifier.class.php 15 2010-05-02 14:41:18Z burak.yueksel $
* @package tnotifier
*/

class Tnotifier 
{
	const API_URL = 'http://api.twitter.com/1/statuses/';
	const CACHE_DIR = 'cache/';	
	const EXTENSION = '.json';
	
	private $config;
	
	private $error = array();
	
	private $notify = null;
	private $sinceId = null;
	private $newId = null;
	
	private $url;
	private $credentials;
	private $proxyEnabled;
	private $proxyServer;
	private $proxyAuth; 
	private $proxyCredentails;
	
	private $jsonData;
	private $notifications = array();
	
	/**
	 * Constructor
	 * 
	 * @param string $configFile
	 */
	public function __construct($configFile='config.ini')
	{
		$this->createCache();
		
		$this->checkNotifySend();
		$this->checkConfigFile($configFile);
		
		$this->getSinceId();
		
		$url = $this->getFeedUrl();
		
		$credentials = $this->getCredentials();
		$proxyEnabled = $this->getProxyEnabled();
		$proxyServer = '';
		$proxyAuth = false;
		$proxyCredentails = '';
		if($proxyEnabled === true) {
			$proxyServer = $this->getProxyServer();
			$proxyAuth = $this->getProxyAuth();
			if($proxyAuth === true) { 
				$proxyCredentails = $this->getProxyCredentials();
			}
		}
		
		$json = $this->getFeed($url, $credentials, $proxyEnabled, $proxyServer, $proxyAuth, $proxyCredentails);
		
		if(!sizeof($this->error)) {
			$this->getNotifications($json);
			$this->displayNotifications();
			$this->storeId();
		} else {
			$this->displayErrors();
		}
	}
	
	/**
	 * Checks if notify-send is installed
	 * 
	 * @param string $pathToBinary
	 */
	private function checkNotifySend($pathToBinary='/usr/bin/notify-send')
	{
		if(file_exists($pathToBinary)) {
			$this->notify = true;
		} else {
			$this->nofify = false;
			$this->setError('notify-send is not installed');
		}
	}
	
	/**
	 * Checks if config file exists
	 * 
	 * @param string $configFile
	 */
	private function checkConfigFile($configFile)
	{
		if(file_exists($configFile)) {
			$this->config = parse_ini_file($configFile, true);
		} else {
			$this->setError('no config file found');
		}
	}
	
	/**
	 * Process json data
	 * 
	 * @param string $json
	 */
	private function getNotifications($json)
	{
		$data = json_decode($json);
		if(is_array($data)) {
			/*
			 * @todo
			 * Because 32 bit systems have a maximum signed integer range of -2147483648 to 2147483647
			 * tweet id needs to be divided by 100
			 */
			$oldId = ($this->sinceId/100);
			$count = sizeof($data) - 1;
			for($i=$count;$i>0;$i--) {
				$stat = $data[$i];
				
				$newId = ($stat->id/100);
				
				if($newId > $oldId) {
					$this->setNewId($stat->id);
				} else {
					continue;
				} 
				
				$user = $stat->user->screen_name;
				$text = str_replace('"', "'", $stat->text);
				$icon = $this->downloadImage($stat->user->id, $stat->user->profile_image_url);
				
				$this->addNotification($user, $text, $icon);
			}
		} else {
			$this->setError('json error');
		}
	}
	
	/**
	 * Returns image name
	 * 
	 * @param int $userId
	 * @param string $imageUrl
	 * @return string
	 */
	private function getImage($userId, $imageUrl)
	{
		$part = explode('/', $imageUrl);
		return self::CACHE_DIR.strtolower($part[count($part)-1]);
	}
	
	/**
	 * returns twitter credentials form config
	 * 
	 * @return string
	 */
	private function getCredentials()
	{
		$username = $this->config['twitter']['username'];
		$password = $this->config['twitter']['password'];
		
		if(!empty($username) and !empty($password)) {
			return $username.':'.$password;
		} else {
			$this->setError('twitter username or password not found, check config.ini');
			return false;
		}
	}
	
	/**
	 * Returns timeline from config
	 * 
	 * @return string
	 */
	private function getTimeline()
	{
		switch($this->config['general']['timeline']) {
			case 'friends':
				$timeline = 'friends_timeline';
				break;
			case 'public':
				$timeline = 'public_timeline';
				break;
			case 'home':
				$timeline = 'home_timeline';
				break;
			case 'user':
				$timeline = 'user_timeline';
				break;
			default:
				$timeline = 'friends_timeline';
		}
		return $timeline;
	}
	
	/**
	 * Returns Feed URL
	 * 
	 * @return string
	 */
	private function getFeedUrl()
	{
		$timeline = $this->getTimeline();
		$url = self::API_URL.$timeline.self::EXTENSION;
		$url .= '?count='.$this->getItemsPerCall();
		if(!empty($this->sinceId)) $url .= '&since_id='.$this->sinceId;
		return $url;
	}
	
	/**
	 * Returns notification display time
	 * 
	 * @return string
	 */
	private function getDisplayTime()
	{
		$displayTime = '3000';
		if(isset($this->config['general']['display_time'])) {
			if(strlen($this->config['general']['display_time'])) {
				$displayTime = $this->config['general']['display_time'];
			}
		} 
		return $displayTime;
	}
	
	/**
	 * Returns item count
	 * 
	 *  @return string
	 */
	private function getItemsPerCall()
	{
		$itemsPerCall = '100';
		if(isset($this->config['general']['items_per_call'])) {
			if(strlen($this->config['general']['items_per_call'])) {
				$itemsPerCall = $this->config['general']['items_per_call'];
			}
		} 
		return $itemsPerCall; 
	}
	
	/**
	 * Returns storage path
	 * 
	 * @return string
	 */
	private function getStorage()
	{
		return self::CACHE_DIR.md5($this->getTimeline());
	}
	
	/**
	 * Returns since id
	 * 
	 * @return int
	 */
	private function getSinceId()
	{
		if(file_exists($this->getStorage())) {
			$this->sinceId = file_get_contents($this->getStorage());
		} else {
			$this->sinceId = 0;
		}
	}
	
	/**
	 * Returns true if proxy support is enabled in config, else it returns false
	 * 
	 * @return boolean
	 */
	private function getProxyEnabled()
	{
		if($this->config['proxy']['enable'] == '1') {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns proxy server and port usable for cURL
	 * 
	 * @return string|boolean
	 */
	private function getProxyServer()
	{
		$server = $this->config['proxy']['server'];
		$port = $this->config['proxy']['port'];
		
		if(!empty($server) and !empty($port)) {
			return $server.':'.$port;
		} else {
			$this->setError('proxy server is enabled but no server url is givven');
			return false;
		}
	}
	
	/**
	 * Returns true if proxy authentication is enabled, else it returns false
	 * 
	 * @return boolean
	 */
	private function getProxyAuth()
	{
		if($this->config['proxy']['auth'] == '1') {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns credentails for proxy server usable for cURL
	 * 
	 * @return string|boolean
	 */
	private function getProxyCredentials()
	{
		$username = $this->config['proxy']['username'];
		$password = $this->config['proxy']['password'];
		
		if(!empty($username) and !empty($password)) {
			return $username.':'.$password;
		} else {
			$this->setError('proxy authentication is enabled but no username or password is given');
			return false;
		}
	}
	
	/**
	 * Returns Feed as JSON string
	 * 
	 * @param string $url
	 * @param string $credentials
	 * @param bool $proxyEnabled[optional]
	 * @param string $proxyServer[optional]
	 * @param bool $proxyAuth[optional]
	 * @param string $proxyCredentails[optional]
	 */
	private function getFeed($url, $credentials, $proxyEnabled=false, 
		$proxyServer='', $proxyAuth=false, $proxyCredentails='')
	{
		// cUrl options
		$opts = array();
		$opts[CURLOPT_URL] = $url;
		$opts[CURLOPT_PORT] = 80;
		$opts[CURLOPT_RETURNTRANSFER] = 1;
		$opts[CURLOPT_FOLLOWLOCATION] = 1;
		$opts[CURLOPT_USERPWD] = $credentials;
		$opts[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
		$opts[CURLOPT_TIMEOUT] = 30;
		$opts[CURLOPT_TIMEOUT] = 30;
		
		if($proxyEnabled) {
			$opts[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
			$opts[CURLOPT_PROXY] = $proxyServer;
			if($proxyAuth) {
				$opts[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;  
				$opts[CURLOPT_PROXYUSERPWD] = $proxyCredentails;
			}
		}
		
		$ch = curl_init();
		curl_setopt_array($ch, $opts);
		$json = curl_exec($ch);
		if($json === false) {
			$this->setError(curl_error($ch));
		}
		curl_close($ch);
		
		return $json;
	}
	
	/**
	 * Sets new since id
	 * 
	 * @param int $newId
	 */
	private function setNewId($newId)
	{
		$this->newId = $newId;
	}
	
	/**
	 * Sets new error
	 * 
	 * @param string $message
	 */
	private function setError($message)
	{
		$this->error[] = array('message' => $message);
	}
	
	/**
	 * Displays error either as notification or as echo
	 */
	private function displayErrors()
	{
		foreach($this->error as $error) {
			if($this->notify) {
				exec('notify-send tnotifier "error: '.$error['message'].'"');
			} else {
				echo "tnotifier error: ".$error['message']."\n";
			}
		}
	}
	
	/**
	 * Displays notifications
	 */
	private function displayNotifications()
	{
		foreach($this->notifications as $noti) {
			$cmd = 'notify-send -t '.$this->getDisplayTime().' ';
			if(strlen($noti['icon'])) {
				$cmd .= '-i '.$noti['icon'].' ';
			}
			$cmd .= $noti['title'].' "'.$noti['content'].'"';
			exec($cmd);
		}
	}
	
	/**
	 * Download images using cUrl
	 * 
	 * @param int $userId
	 * @param string $imageUrl
	 * @param bool $proxyEnabled[optional]
	 * @param string $proxyServer[optional]
	 * @param bool $proxyAuth[optional]
	 * @param string $proxyCredentails[optional]
	 * @return string
	 */
	private function downloadImage($userId, $imageUrl, $proxyEnabled=false, 
		$proxyServer='', $proxyAuth=false, $proxyCredentails='')
	{
	 	$icon = '';
	 	
		$download = $this->config['general']['download_images'];
	 	if($download == '1') {
	 		$image = $this->getImage($userId, $imageUrl);
			if(!file_exists($image)) {
				$fp = fopen($image, 'a+');
				$ch = curl_init($imageUrl);
				$opts = array();
				$opts[CURLOPT_FILE] = $fp;
				if($proxyEnabled) {
					$opts[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
					$opts[CURLOPT_PROXY] = $proxyServer;
					if($proxyAuth) {
						$opts[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;  
						$opts[CURLOPT_PROXYUSERPWD] = $proxyCredentails;
					}
				}
				curl_setopt_array($ch, $opts);
				$res = curl_exec($ch);
				if($res === false) {
					$this->setError(curl_error($ch));
					$icon = '';	
				} else {
					$icon = realpath($image);	
				} 
				curl_close($ch);
				fclose($fp); 
			} else {
				$icon = realpath($image);
			}
		}
		
		return $icon;
	}
	
	/**
	 * Stores new since id on filesystem
	 */
	private function storeId()
	{
		if($this->newId !== null) {
			$fp = fopen($this->getStorage(), 'w+');
			fwrite($fp, $this->newId);
			fclose($fp);
		}
	}
	
	/**
	 * Creates cache dir if doesn't exists
	 */
	private function createCache()
	{
		if(!file_exists(self::CACHE_DIR)) mkdir(CACHE_DIR, 0777);
	}
	
	/**
	 * Adds new notification
	 * 
	 * @param string $title
	 * @param string $content
	 * @param string $icon
	 */
	private function addNotification($title, $content, $icon)
	{
		$this->notifications[] = array('title' => $title, 
									   'content' => $content, 
									   'icon' => $icon);
	}
}